<template>
  <div class="containers">
    <div v-if="!$route.query.type === 'view'" class="toolbar">
      <div style="margin-bottom: 20px; font-size: 18px">新建设计</div>
      <el-button size="mini" icon="el-icon-refresh" @click="refresh" />
      <el-button size="mini" icon="el-icon-plus" @click="handleZoom(0.1)" />
      <el-button size="mini" icon="el-icon-minus" @click="handleZoom(-0.1)" />
    </div>
    <div ref="canvas" class="canvas" />
    <panel
      v-if="bpmnModeler"
      ref="panel"
      :process-id="processId"
      :modeler="bpmnModeler"
      @update="json2xml"
      @set-default="setNodeDefault"
      @set-error="setNodeError"
    />
    <!-- <div class="toolbar">
      <a title="download">下载</a>
      <a ref="saveDiagram" href="javascript:" title="download BPMN diagram">BPMN</a>
      <a ref="saveSvg" href="javascript:" title="download as SVG image">SVG</a>
    </div>-->
  </div>
</template>

<script>
//  import BpmnModeler from 'bpmn-js/lib/Modeler' // bpmn-js 设计器
import { mapMutations, mapState } from 'vuex'
import mock from '../mock/mock'
import BpmData from './bpmData'
import camundaExtension from './camunda.json'
import customTranslate from './customBpmn/custom/customTranslate' // 汉化
import { CustomModeler } from './customBpmn/index'
import panel from './PropertyPanel' // 属性面板
import { getProcessInfo, getProcessDetail } from '@/api/ticket/processDesign'

export default {
  components: {
    panel
  },
  data() {
    return {
      res: [],
      bpmnModeler: null,
      element: null,
      bpmData: new BpmData(),
      // chart变量流程图xml文件数据
      bpmnXmlStr: `<?xml version="1.0" encoding="UTF-8"?>
        <bpmn:definitions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" id="Definitions_1" targetNamespace="http://bpmn.io/schema/bpmn">
          <bpmn:process id="Process_1" isExecutable="true">
            <bpmn:startEvent id="StartEvent_1" />
          </bpmn:process>
          <bpmndi:BPMNDiagram id="BPMNDiagram_1">
            <bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Process_1">
              <bpmndi:BPMNShape id="_BPMNShape_StartEvent_2" bpmnElement="StartEvent_1">
                <dc:Bounds x="240" y="102" width="36" height="36" />
              </bpmndi:BPMNShape>
            </bpmndi:BPMNPlane>
          </bpmndi:BPMNDiagram>
        </bpmn:definitions>
    `,
      scale: 1,
      // 汉化
      customTranslateModule: {
        translate: ['value', customTranslate]
      },
      // 当前流程id
      processId: '',
      //  当前操作类型
      handleType: null
    }
  },
  computed: {
    ...mapState({
      // 节点表单信息
      nodeInfo: (state) => state.bpmn.nodeInfo,
      // 节点连线信息
      condition: (state) => state.bpmn.condition,
      //  获取当前系统主题
      sideTheme: (state) => state.settings.sideTheme
    })
  },
  mounted() {
    const canvas = this.$refs.canvas
    this.processId = this.$route.query.id
    this.handleType = this.$route.query.type
    // 生成实例
    this.bpmnModeler = new CustomModeler({
      container: canvas,
      moddleExtensions: { camunda: camundaExtension },
      // 是否可以拖拽
      additionalModules:
          this.handleType === 'view'
            ? [
              // this.customTranslateModule,
              {
                // translate: ['value', ''],
                paletteProvider: ['value', ''], // 禁用/清空左侧工具栏
                labelEditingProvider: ['value', ''], // 禁用节点编辑
                contextPadProvider: ['value', ''], // 禁用图形菜单
                bendpoints: ['value', {}], // 禁用连线拖动
                zoomScroll: ['value', ''], // 禁用滚动
                moveCanvas: ['value', ''], // 禁用拖动整个流程图
                move: ['value', ''] // 禁用单个图形拖动
              }
            ]
            : [this.customTranslateModule]
    })
    if (this.processId) {
      //  预览、编辑、复制
      // const processInfo = JSON.parse(sessionStorage.getItem('processInfo'))
      // const _nodeInfo = JSON.parse(processInfo.nodeInfo)
      // const _condition = JSON.parse(processInfo.nodeCondition)
      // this.getNodeInfo(_nodeInfo)
      // this.getCondition(_condition)
      // this.createNewDiagram(processInfo.xmlResource)
      getProcessDetail(this.$route.query.id).then(res => {
        const _nodeInfo = JSON.parse(res.nodeInfo)
        const _condition = JSON.parse(res.nodeCondition)
        this.getNodeInfo(_nodeInfo)
        this.getCondition(_condition)
        this.createNewDiagram(res.xmlResource)
      })
    } else if (this.$route.query.flowId) {
      //  从工单查看流程
      getProcessInfo({ 'id': this.$route.query.flowId }).then(res => {
        const _nodeInfo = JSON.parse(res.nodeInfo)
        const _condition = JSON.parse(res.nodeCondition)
        this.getNodeInfo(_nodeInfo)
        this.getCondition(_condition)
        this.createNewDiagram(res.xmlResource)
      })
    } else {
      // 新增流程定义
      this.createNewDiagram()
    }
    this.setNodeColor()
  },
  methods: {
    ...mapMutations({
      getNodeInfo: 'bpmn/getNodeInfo',
      getCondition: 'bpmn/getCondition',
      changeNodeInfo: 'bpmn/changeNodeInfo',
      deleteNodeInfo: 'bpmn/deleteNodeInfo'
    }),
    createNewDiagram(xml = this.bpmnXmlStr) {
      // 将字符串转换成图显示出来
      this.bpmnModeler.importXML(xml, (err) => {
        if (err) {
          console.error(err)
        } else {
          this.addEventBusListener()
          this.adjustPalette()
          this.setNodeColor()
        }
      })
    },
    // 调整左侧工具栏排版
    adjustPalette() {
      try {
        // 获取 bpmn 设计器实例
        const canvas = this.$refs.canvas
        const djsPalette = canvas.children[0].children[1].children[4]
        const djsPalStyle = {
          // 模拟去掉左侧栏
          display: this.handleType === 'view' ? 'none' : 'block',
          width: '130px',
          padding: '5px',
          background: this.sideTheme === 'theme-dark' || this.sideTheme === 'tech' ? 'transparent' : 'white',
          left: '20px',
          borderRadius: 0,
          border: this.sideTheme === 'theme-dark' || this.sideTheme === 'tech' ? '1px solid rgba(59, 139, 255, 0.4)' : ''
        }
        for (var key in djsPalStyle) {
          djsPalette.style[key] = djsPalStyle[key]
        }
        const palette = djsPalette.children[0]
        const allGroups = palette.children

        // 修改控件样式
        for (var gKey in allGroups) {
          const group = allGroups[gKey]
          for (var cKey in group.children) {
            const control = group.children[cKey]
            const controlStyle = {
              display: 'flex',
              justifyContent: 'flex-start',
              alignItems: 'center',
              width: '100%',
              padding: '5px',
              color: this.sideTheme === 'theme-dark' || this.sideTheme === 'tech' ? '#fff' : '#333'
            }
            if (
              control.className &&
              control.dataset &&
              control.className.indexOf('entry') !== -1
            ) {
              const controlProps = this.bpmData.getControl(
                control.dataset.action
              )
              control.innerHTML = `<div style='font-size: 14px;font-weight:500;margin-left:15px;color: ${this.sideTheme === 'theme-dark' || this.sideTheme === 'tech' ? '#fff' : '#333'};'>${controlProps['title']}</div>`
              for (var csKey in controlStyle) {
                control.style[csKey] = controlStyle[csKey]
              }
            }
          }
        }
      } catch (e) {
        console.log(e)
      }
    },

    // 设置节点颜色
    setNodeColor() {
      // 目的：为第一个节点添加绿色，为第二个节点添加黄色
      // 实现步骤：1、找到页面里所有节点
      if (!this.$route.query.taskKey) return
      const elementRegistry = this.bpmnModeler.get('elementRegistry')
      const shape = elementRegistry.get(this.$route.query.taskKey)
      const shapes = [shape]

      if (shape) {
        this.bpmnModeler.get('modeling').setColor(shapes, { stroke: 'orange' })
      }
      // this.nodeList = elementRegistry.filter(
      //   (item) =>{
      //     item.type === "bpmn:UserTask" || item.type === "bpmn:ServiceTask"}
      // );
      // 此时得到的userTaskList 便是流程图中所有的节点的集合
      // console.log(this.nodeList);
      // 步骤2 ：为节点添加颜色
      // 方式1 ：modeling.setColor(参数1：节点，可以是单个元素实例，也可是多个节点组成的数组，参数2：class类);
      // let modeling = this.bpmnModeler.get("modeling");
      // modeling.setColor(this.nodeList[0], {
      //   stroke: "green",
      //   fill: "yellow",
      // });
    },
    //  设置节点报错颜色
    setNodeError(nodes = []) {
      const elementRegistry = this.bpmnModeler.get('elementRegistry')
      nodes.forEach(node => {
        const shape = elementRegistry.get(node)
        const shapes = [shape]
        if (shape) {
          this.bpmnModeler.get('modeling').setColor(shapes, { stroke: 'red' })
        }
      })
    },
    //  设置节点默认颜色
    setNodeDefault(node) {
      const elementRegistry = this.bpmnModeler.get('elementRegistry')
      const shape = elementRegistry.get(node)
      const shapes = [shape]
      if (shape) {
        this.bpmnModeler.get('modeling').setColor(shapes, { stroke: 'black' })
      }
    },
    // 放大缩小
    handleZoom(flag) {
      if (flag < -0.7 && this.scale <= 1) {
        return
      }
      this.scale += flag
      this.$nextTick(() => {
        this.bpmnModeler.get('canvas').zoom(this.scale)
      })
    },
    // 刷新
    refresh() {
      this.createNewDiagram()
      this.scale = 1
      this.handleZoom(0)
    },
    // 导出数据
    handleDownload() {
      this.bpmnModeler.saveXML({ format: true }, (err, data) => {
        console.log(err, data)
        const dataTrack = 'bpmn'
        const a = document.createElement('a')
        const name = `diagram.${dataTrack}`
        a.setAttribute(
          'href',
          `data:application/bpmn20-xml;charset=UTF-8,${encodeURIComponent(
            data
          )}`
        )
        a.setAttribute('target', '_blank')
        a.setAttribute('dataTrack', `diagram:download-${dataTrack}`)
        a.setAttribute('download', name)
        document.body.appendChild(a)
        a.click()
        document.body.removeChild(a)
      })
    },
    // 导入
    handleOnchangeFile(file) {
      const reader = new FileReader()
      let data = ''
      reader.readAsText(file.raw)
      reader.onload = (event) => {
        data = event.target.result
        this.bpmnModeler.importXML(data, (err) => {
          if (err) {
            this.$message.info('导入失败')
          } else {
            this.$message.success('导入成功')
          }
        })
      }
    },
    // 注册节点
    addEventBusListener() {
      const eventBus = this.bpmnModeler.get('eventBus')
      const Modeling = this.bpmnModeler.get('modeling')
      // 新增节点
      this.bpmnModeler.on('shape.add', (e) => {
        const { element } = e
        if (element.id.includes('Flow_')) return
        this.$refs.panel.formVisibility = false
        this.changeNodeInfo({ [element.id]: {}})
      })
      // 删除节点时
      this.bpmnModeler.on('shape.removed', (e) => {
        const { element } = e
        this.$refs.panel.formVisibility = false
        this.deleteNodeInfo(element.id)
      })
      // 注册节点事件，eventTypes中可以写多个事件
      const eventTypes = ['element.click', 'shape.changed', 'element.changed']
      eventTypes.forEach((eventType) => {
        eventBus.on(eventType, (e) => {
          const { element } = e
          // console.log('------------element-------------', e)
          if (!element.parent) return

          if (!e || element.type === 'bpmn:Process') {
            return false
          } else {
            if (eventType === 'element.click') {
              // console.log('进到节点点击判断0-0----', element)
              // 编辑时不允许对图形修改
              const contentBox = document.querySelector('.djs-context-pad')
              if (this.handleType === 'view') {
                contentBox.style.display = 'none'
                this.$refs.panel.editable = false
                contentBox.addEventListener('click', () => {
                  this.$refs.panel.formVisibility = false
                })
              }
              //  判断右侧业务表单是否需要展示隐藏字段
              let userAmount = 0

              element.parent.children.forEach((item) => {
                item.businessObject.showUserMore = false
                if (item.type === 'bpmn:UserTask') {
                  userAmount++
                }
                if (userAmount > 1) {
                  item.businessObject.showUserMore = true
                }
              })
              this.$refs.panel.formVisibility = true
              const businessObject = element.businessObject || element

              // 此时的businessObject 是我们想要的万能的小可爱
              this.splitBusiness2Json(businessObject, element)
              // 节点点击后想要做的处理
              // 此时想要点击节点后，拿到节点实例，通过外部输入更新节点名称
              // this.currentElement = element
            }
            if (eventType === 'element.changed') {
              // console.log(element, '>>> element in element.changed')
            }
            if (eventType === 'shape.changed') {
              // console.log(element, '>>> element in shape.changed')
            }
          }
        })
      })
    },

    /* 解析xml */
    splitBusiness2Json(businessObject, element) {
      const formData = {}
      //  所有节点
      const _originArr = element.parent?.children
      //  所有元素节点
      const _nodeArr = _originArr
        .filter((item) => item.type !== 'bpmn:SequenceFlow')
        .map((item) => {
          return {
            id: item.id,
            flow: [],
            type: item.type
          }
        })
      //  所有连线节点
      const _lineArr = _originArr
        .filter((item) => item.type === 'bpmn:SequenceFlow')
        .map((item) => {
          return {
            id: item.id,
            source: item.source.id,
            target: item.target.id,
            flow: []
          }
        })

      //  根据连线节点的 source 和 target，将一条流程上的节点id，保存起来
      //  在给formData赋值的时候，可以新增一个去存储上级节点信息
      const _flow = this.setFlowNode(_nodeArr, _lineArr)

      // 此时这个id必须要，因为json生成xml时，节点id是找到节点的唯一标识
      formData['id'] = businessObject.id
      const params = this.getExtensionElement(
        businessObject,
        // 'camunda:InputOutput'
        'camunda:FormData'
      )
      if (params && params.inputParameters) {
        params.inputParameters.forEach((item) => {
          const definition = item.definition
          if (definition) {
            if (definition.$type === 'camunda:List') {
              const arr = []
              definition.items.forEach((itemsItem) => {
                arr.push(itemsItem.value)
              })
              formData[item.name] = arr
            } else if (definition.$type === 'camunda:Map') {
              const obj = {}
              if (definition.entries) {
                definition.entries.forEach((entriesItem) => {
                  obj[entriesItem.key] = entriesItem.value
                })
                formData[item.name] = obj
              }
            }
          } else {
            formData[item.name] = item.value
          }
        })
      }
      const type = element.type
      this.$refs.panel.showFlowMore = false
      // 区分点击节点
      if (type.includes('SequenceFlow')) {
        // 连线
        // 前面是互斥网关，后面是用户任务
        if (
          element.source?.type?.includes('ExclusiveGateway') &&
          element.target?.type?.includes('UserTask')
        ) {
          this.$refs.panel.showFlowMore = true
          this.$refs.panel.formData = {
            ...JSON.parse(JSON.stringify(mock.flowFormData)),
            nodeName: businessObject.name,
            id: businessObject.id,
            type: type,
            nodeId: businessObject.id,
            ...this.condition[businessObject.id]
          }
          //  给线节点添加前置节点：互斥网关需要设置条件
          this.$refs.panel.flowArr = _flow.line.find(item => item.id === businessObject.id)
          this.$refs.panel.formValidFlag = false
        } else {
          // console.log('普通连线')
          this.$refs.panel.formData = {
            ...JSON.parse(JSON.stringify(mock.normalFormData)),
            type: type,
            nodeName: businessObject.name,
            ...formData
          }
        }
      } else if (type.includes('UserTask')) {
        // 用户任务
        // console.log('用户任务')
        this.$refs.panel.formData = {
          ...JSON.parse(JSON.stringify(mock.userFormData)),
          id: businessObject.id,
          type: type,
          nodeName: businessObject.name,
          nodeId: businessObject.id,
          ...this.nodeInfo[businessObject.id]
        }
        this.$refs.panel.showUserMore = businessObject.showUserMore
        this.$refs.panel.formValidFlag = false
        //  用户任务的上有节点：处理驳回问题
        this.$refs.panel.flowArr = _flow.node.find(item => item.id === businessObject.id)
      } else {
        this.$refs.panel.formData = {
          ...JSON.parse(JSON.stringify(mock.normalFormData)),
          type: type,
          nodeName: businessObject.name,
          ...formData
        }
      }
    },

    //  设置当前节点的前置节点
    setFlowNode(node, line) {
      // 创建一个map，用于将id与flow一起存储
      const map = new Map(node.map(obj => [obj.id, obj.flow]))
      // 遍历数组线节点中的每个对象，将源点添加到目标点的flow数组中
      line.forEach(obj => {
        const { source, target } = obj
        const sourceFlow = map.get(source)
        const targetFlow = map.get(target)
        targetFlow.push(source, ...sourceFlow)
      })

      const newNode = node.map(obj => ({
        id: obj.id,
        flow: map.get(obj.id)
      }))

      line.forEach(obj => {
        const sourceNode = newNode.find(node => node.id === obj.source)
        obj.flow = sourceNode.flow
      })

      return { 'line': line, 'node': newNode }
    },

    getExtensionElement(element, type) {
      if (!element.extensionElements) {
        return
      }
      return element.extensionElements.values.filter((extensionElement) => {
        return extensionElement.$instanceOf(type)
      })[0]
    },

    /* 转换为json */
    json2xml(formData, nodeType, formTemplate) {
      const elementRegistry = this.bpmnModeler.get('elementRegistry')
      const bpmnFactory = this.bpmnModeler.get('bpmnFactory')
      const modeling = this.bpmnModeler.get('modeling')
      // “elementRegistry.get” 根据节点id找到节点实例，为其在xml写入添加属性
      const element = elementRegistry.get(formData.id)
      // 存在找不到节点的情况，那说明节点在点击后，被删除了
      if (element !== undefined) {
        //  需要更新 userTask
        if (nodeType === 'userTask') {
          const extensionElements = bpmnFactory.create('bpmn:ExtensionElements')
          const FormData = bpmnFactory.create('camunda:FormData')
          extensionElements.values = [FormData]
          FormData.fields = []

          // 遍历this.form，将其各个属性进行循环写入extensionElements
          for (let i = 0; i < formTemplate.length; i++) {
            let FormField = null
            if (formTemplate[i].component !== 'grid') {
              FormField = bpmnFactory.create('camunda:FormField', {
                id: formTemplate[i].id,
                label: formTemplate[i].label,
                type: formTemplate[i].type
              })
              FormField !== null && FormData.fields.push(FormField)
            } else {
              formTemplate[i].list && formTemplate[i].list.forEach(j => {
                j?.list.forEach(k => {
                  FormField = bpmnFactory.create('camunda:FormField', {
                    id: k.model,
                    label: k.name,
                    type: k.type === 'time' || k.type === 'date' ? 'date' : 'string'
                  })
                })
                FormField !== null && FormData.fields.push(FormField)
              })
            }
          }

          const properties = bpmnFactory.create('camunda:Properties')
          const property = bpmnFactory.create('camunda:Property')
          property.name = 'hangUp'
          property.value = formData.hangUp === true ? 'true' : 'false'
          properties.get('values').push(property)

          //  添加一个 camunda:taskListener 处理 表单相关信息
          const taskListener = bpmnFactory.create('camunda:TaskListener', {
            event: 'create',
            class: 'com.hkinfo.ticket.order.listener.OrderTaskAssignmentListener'
          })
          extensionElements.values = [FormData, taskListener, properties]
          modeling.updateProperties(element, { extensionElements })
        }

        //  需要更新 sequenceFlow: 此时的formTemplate是表达式expression吗，参数名改动比较多暂时用这个
        if (nodeType === 'sequenceFlow') {
          const conditionExpression = bpmnFactory.create('bpmn:FormalExpression', {
            'body': '${' + formTemplate + '}'
          })
          modeling.updateProperties(element, { conditionExpression })
        }
      }
      this.showXml()
    },

    showXml() {
      this.bpmnModeler.saveXML({ format: true }, (err, xml) => {
        console.log(xml)
      })
    }
  }
}
</script>

<style lang="scss" scoped>
/*左边工具栏以及编辑节点的样式*/
@import "~bpmn-js/dist/assets/diagram-js.css";
@import "~bpmn-js/dist/assets/bpmn-font/css/bpmn.css";
@import "~bpmn-js/dist/assets/bpmn-font/css/bpmn-codes.css";
@import "~bpmn-js/dist/assets/bpmn-font/css/bpmn-embedded.css";

.containers {
  position: relative;
  background-color: #ffffff;
  width: 100%;
  height: 100%;
  padding-top: 10px;
  display: flex;
  flex-direction: column;
  .canvas {
    // width: calc(100% - 300px);
    width: 100%;
    height: 100%;
  }
  .panel {
    position: absolute;
    right: 0;
    top: 50px;
    width: 300px;
  }
  .toolbar {
    // position: absolute;
    // top: 20px;
    // right: 350px;
    padding: 20px;
    a {
      text-decoration: none;
      margin: 5px;
      color: #409eff;
    }
  }
}
::v-deep {
  .bjs-powered-by,
  .group[data-group="create"],
  .entry[data-action="append"] {
    display: none;
  }
  .group[data-group="model"],
  .group[data-group="edit"] {
    display: inline-flex;
    flex-direction: column;
    flex-wrap: wrap;
  }
  //开始节点输入数字后会出现左下角莫名产生ul，找不到哪加的，隐藏掉html
  .bjs-breadcrumbs {
    display: none;
  }
}

</style>
<style lang="scss">
body {
  width: 100vw;
  height: 100vh;
  #app {
    width: 100%;
    height: 100%;
  }
}
* {
  box-sizing: border-box;
}
</style>
